/*
 * LockStepAspect.aj
 * 
 *  author: Daniel Rudigier
 *    date: 18.11.2008
 * 
 * 
 */

package at.syncme.aspects.algorithm;


import at.syncme.aspects.GameAspect;

import at.syncme.framework.Algorithm;
import at.syncme.framework.Event;
import at.syncme.framework.GameState;
import at.syncme.framework.Network;
import at.syncme.framework.legacy.WrapperCache;
import at.syncme.framework.midp.SynchronizedQueue;
import de.enough.polish.util.Arrays;
import de.enough.polish.util.HashMap;

/**
 * this will introduce lock-step behavior. cross-cuts incoming network events,
 * event handling and game update.
 * <ul>
 * <li>incoming network events have to be observed (notify)
 * <li>the type of this event has to be checked (lock-step specific)
 * <li>game update has to be advised, to restrict game advance.
 * </ul>
 * <p>
 * lock-step is conservative and tries to avoid synchronization issues by
 * forcing all participants to wait until everyone broadcasts that he is ready.
 * the global virtual time then won't update the same way like a wall-clock time
 * and is therefore "purely" virtual.
 * </p>
 * 
 * @author Daniel Rudigier
 */
public class LockStep extends Algorithm {
    /**
     * the lock-step ready event
     */
    private static final short LOCKSTEP_READY = Event.id();

    private static LockStep instance;

    // observe the game state
    private GameState gs;

    private final Event readyEvent = Event.create(LOCKSTEP_READY, Event.PRIORITY_HIGH);

    // the known members, that are checked when trying to proceed
    private HashMap members = new HashMap();
    
    private SynchronizedQueue incoming = new SynchronizedQueue();

    // my id
    private final Short me = WrapperCache.valueOf((short)-1);

    /**
     * constructor
     * 
     * @param nw
     */
    protected LockStep(Network nw) {
        super(nw);
    }

    /**
     * constructor
     * 
     * @param nw
     * @return
     */
    public static LockStep getInstance(Network nw) {
        if (instance == null) {
            instance = new LockStep(nw);
        }

        return instance;
    }

    /**
     * initialize lock-step
     * 
     * @param game
     */
    public void init() {
        this.gs = game.getGs();
        this.members.put(this.me, WrapperCache.valueOf(this.gs.getGvt()));
        
        System.out.println("starting thread");
        new Thread(this, LockStep.class.getName()).start();
    }

    /**
     * check if all members are ready
     */
    private void gameUpdate() {
        if (this.members.size() > 1) {
            while (!isReady()) {
                System.out.println("lockstep: not ready for clock advance");
                beFair();
            }
            
            // now we update ourself, invalidate and wait for others to update
            invalidate();
            this.members.put(this.me, WrapperCache.valueOf(this.gs.getGvt()));
            
            // reuse the event object
            this.readyEvent.setCreated(this.gs.getGvt());
            sendEvent(this.readyEvent);
        }
    }

    /**
     * invalidate everything, all others have to acknowledge
     */
    private void invalidate() {
        synchronized (this.members) {
            Short[] keys = new Short[this.members.size()];
            Arrays.cast(this.members.keys(), keys);

            for (int i = 0; i < keys.length; i++) {
                this.members.put(keys[i], WrapperCache.valueOf((long)-1));
            }
        }
    }

    /**
     * return if the game is ready to proceed
     * 
     * @return true, if lock-step is ready
     */
    private boolean isReady() {
        boolean ret = true;
        Long[] values = new Long[this.members.size()];
        Arrays.cast(this.members.values(), values);

        for (int i = 0; i < values.length; i++) {
            if (values[i].longValue() == -1) {
                ret = false;
                break;
            }
        }

        return ret;
    }

    /**
     * event received from anywhere (local or network)
     * 
     * @see at.syncme.framework.Algorithm#eventReceived(at.syncme.framework.Event)
     */
    public void eventReceived(final Event e) {
        beLazy(500);
        
        System.out.println((e.isLocal() ? "local" : "remote") + " event: " + e);
        
        synchronized(this) {
            this.incoming.add(e);
            
            notifyAll();
        }
    }
    
    /**
     * override
     *
     * @see java.lang.Runnable#run()
     */
    public void run() {
        try {
            while (this.game.isRunning()) {            
                Event e = null;
                synchronized(this) {
                    while (this.incoming.size() == 0) {
                        System.out.println("waiting for next event");
                        beFair();
                    }
                    e = (Event) this.incoming.remove();
                }
                
                // update the state of the event origin
                Short node = WrapperCache.valueOf(e.getOrigin());
                try {
                    // on a regular gvt transmission
                    this.members.put(node, new Long(e.getCreated()));
                } catch (NumberFormatException ex) {
                    // invalidate this boy
                    this.members.put(node, WrapperCache.valueOf((long)-1));
                }
                
                // delegate downwards
                if (e.getId() != readyEvent.getId()) {
                    this.game.gameEvent(e);
                }
            }
        } catch (Exception ex) {
            System.out.println("error in lockstep: ");
            ex.printStackTrace();
        } finally {
            System.out.println("lockstep finished");
        }
    }

    /**
     * some hooks for
     * 
     * @author Daniel Rudigier
     */
    private static aspect LockStepAspect extends GameAspect {        
        after(): gameInit() {
            instance.init();
            System.out.println("lockstep: init");
        }

        before(): gameEnd() {
            System.out.println("lockstep: stopping game");
        }

        /**
         * before advancing, check if the game state can advance
         */
        before(): gameUpdate() {
            instance.gameUpdate();
        }

    }

}
